/*
 * Copyright (c) 2012, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bmi.gwt.ws.client;

import org.bmi.gwt.ws.client.JsWebSocket.WebSocketEventHandler;
import org.bmi.gwt.ws.shared.PayloadSerializer;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.rpc.SerializationException;

/**
 * Encapsulates web socket functionalities to enable objects transmission.
 * 
 * @param <T> The type of serialization designed object.
 * 
 * @author Marco Tamburelli
 */
public class WsConnection<T extends IsSerializable>
{
	private JsWebSocket ws;
	private PayloadSerializer<T> serializer;

	/**
	 * Used by an inner event handler to extends semantic.
	 * 
	 * @param <Q> The type of serialization designed object
	 */
	public static interface WsEventHandler<Q extends IsSerializable>
	{
		/**
		 * It's called when the connection has been opened.
		 */
		void onOpen();

		/**
		 * It's called when the connection has been closed.
		 */
		void onClose();

		/**
		 * It's called when an error occurs.
		 * 
		 * @param ex Could be an exception thrown while performing some locval
		 *        operation, or can be null in case the fault is not in the
		 *        local code.
		 */
		void onError(Exception ex);

		/**
		 * It's Called when an object has been deserialized and received.
		 * 
		 * @param obj The received object.
		 */
		void onObject(Q obj);
	}

	/**
	 * Protocols of data transmission to a server.
	 */
	public static enum Protocol {
		HTTP {
			@Override
			public String toString()
			{
				return "http:";
			}
		},
		HTTPS {
			@Override
			public String toString()
			{
				return "https:";
			}
		},
		WS {
			@Override
			public String toString()
			{
				return "ws:";
			}
		},
		WSS {
			@Override
			public String toString()
			{
				return "wss:";
			}
		}
	}

	/**
	 * Constructor.
	 * 
	 * @param serializer The inner serializer.
	 * @param url Service URL.
	 */
	public WsConnection(PayloadSerializer<T> serializer, String url)
	{
		this.ws = JsWebSocket.create(url);
		this.serializer = serializer;
	}

	/**
	 * Constructor.
	 * 
	 * @param serializer The inner serializer.
	 * @param url Service URL.
	 * @param handler The event handler for Web Socket event handling.
	 */
	public WsConnection(PayloadSerializer<T> serializer, String url, WsEventHandler<T> handler)
	{
		this(serializer, url);

		setEventHandler(handler);
	}

	public void setEventHandler(final WsEventHandler<T> handler)
	{
		this.ws.setEventHandler(new WebSocketEventHandler()
		{
			@Override
			public void onOpen()
			{
				handler.onOpen();
			}

			@Override
			public void onMessage(String message)
			{
				try
				{
					handler.onObject(WsConnection.this.serializer.deserialize(message));
				}
				catch (SerializationException e)
				{
					handler.onError(e);
				}
			}

			@Override
			public void onError()
			{
				handler.onError(null);
			}

			@Override
			public void onClose()
			{
				handler.onClose();
			}
		});
	}

	public int getReadyState()
	{
		return this.ws.getReadyState();
	}

	public int getbufferedAmount()
	{
		return this.ws.getbufferedAmount();
	}

	/**
	 * Serializes and sends an object.
	 * 
	 * @param obj
	 * @throws SerializationException
	 */
	public void send(T obj) throws SerializationException
	{
		this.ws.send(this.serializer.serialize(obj));
	}

	/**
	 * Closes the connection.
	 */
	public void close()
	{
		this.ws.close();
	}

	/**
	 * Creates the full URL from a single file path.
	 * 
	 * @param p The provided protocol.
	 * @param relativeFile The path relative to the root directory.
	 * @return
	 */
	public static String createAbsoluteUrl(Protocol p, String relativeFile)
	{
		return GWT.getModuleBaseURL().replace(Window.Location.getProtocol() + "//", p.toString() + "//")
				.replace(GWT.getModuleName() + "/", relativeFile);
	}
}
